package cn.core.cron.service;

import cn.core.cron.eo.CronJob;
import cn.core.cron.mapper.CronJobMapper;
import cn.core.cron.qo.CronJobQO;
import cn.core.cron.vo.CronJobVO;
import cn.core.tool.exception.CoreException;
import cn.core.tool.util.CoreTool;
import cn.core.tool.util.Paging;
import cn.core.tool.util.Param;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import lombok.extern.log4j.Log4j2;
import org.quartz.*;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;

/**
 * 定时任务 服务
 */
@Log4j2
@Service
public class CronJobService {

  public static final String triggerName = "定时器";

  public static final String executeOnceImmediatelyName = "立即执行一次";

  @Resource
  private CronJobMapper cronJobMapper;

  @Resource
  private Scheduler scheduler;

  @Resource
  private Gson gson;

  /**
   * 定时任务 >>> 保存
   */
  @Transactional(propagation = Propagation.REQUIRED, readOnly = false, rollbackFor = Exception.class)
  public CronJob save(CronJob eo) throws Exception {
    try {
      if (CoreTool.isNullEo(eo)) {
        throw new CoreException(CoreException.E_PARAM_ERR);
      }
      eo.setId(CoreTool.getUUID32());
      eo.setJobStatus("stop");
      cronJobMapper.save(eo);
      return eo;
    } catch (Exception e) {
      if (log.isDebugEnabled()) {
        log.error(e.getMessage(), e);
      }
      throw e;
    }
  }

  /**
   * 定时任务 >>> 批量保存
   */
  @Transactional(propagation = Propagation.REQUIRED, readOnly = false, rollbackFor = Exception.class)
  public Collection<CronJob> saveBatch(Collection<CronJob> eos) throws Exception {
    try {
      if (CoreTool.isEmpty(eos)) {
        throw new CoreException(CoreException.E_PARAM_ERR);
      }
      for (CronJob eo : eos) {
        if (CoreTool.isNullEo(eo)) {
          throw new CoreException(CoreException.E_PARAM_ERR);
        }
        eo.setId(CoreTool.getUUID32());
      }
      cronJobMapper.saveBatch(new Paging<>(), eos);
      return eos;
    } catch (Exception e) {
      if (log.isDebugEnabled()) {
        log.error(e.getMessage(), e);
      }
      throw e;
    }
  }

  /**
   * 定时任务 >>> 修改
   */
  @Transactional(propagation = Propagation.REQUIRED, readOnly = false, rollbackFor = Exception.class)
  public CronJob update(CronJob eo) throws Exception {
    try {
      if (CoreTool.isNullEo(eo) || CoreTool.isNullStr(eo.getId())) {
        throw new CoreException(CoreException.E_PARAM_ERR);
      }
      CronJob oeo = cronJobMapper.pk(new CronJobQO().setId(eo.getId()));
      if (CoreTool.isNullEo(oeo)) {
        throw new CoreException(CoreException.E_PARAM_ERR);
      }
      if (eo.getVersions() != null ? !eo.getVersions().equals(oeo.getVersions()) : oeo.getVersions() != null ? true : false) {
        throw new CoreException(CoreException.E_VERSIONS_ERR);
      }
      cronJobMapper.update(eo);
      return eo;
    } catch (Exception e) {
      if (log.isDebugEnabled()) {
        log.error(e.getMessage(), e);
      }
      throw e;
    }
  }

  /**
   * 定时任务 >>> 批量修改
   */
  @Transactional(propagation = Propagation.REQUIRED, readOnly = false, rollbackFor = Exception.class)
  public Collection<CronJob> updateBatch(Collection<CronJob> eos) throws Exception {
    try {
      if (CoreTool.isEmpty(eos)) {
        throw new CoreException(CoreException.E_PARAM_ERR);
      }
      for (CronJob eo : eos) {
        if (CoreTool.isNullEo(eo) || CoreTool.isNullStr(eo.getId())) {
          throw new CoreException(CoreException.E_PARAM_ERR);
        }
        CronJob oeo = cronJobMapper.pk(new CronJobQO().setId(eo.getId()));
        if (CoreTool.isNullEo(oeo)) {
          throw new CoreException(CoreException.E_PARAM_ERR);
        }
//        if (eo.getVersions() != null ? !eo.getVersions().equals(oeo.getVersions()) : oeo.getVersions() != null ? true : false) {
//          throw new CoreException(CoreException.E_VERSIONS_ERR);
//        }
      }
      cronJobMapper.updateBatch(new Paging<>(), eos);
      return eos;
    } catch (Exception e) {
      if (log.isDebugEnabled()) {
        log.error(e.getMessage(), e);
      }
      throw e;
    }
  }

  /**
   * 定时任务 >>> 删除
   */
  @Transactional(propagation = Propagation.REQUIRED, readOnly = false, rollbackFor = Exception.class)
  public void remove(CronJob eo) throws Exception {
    try {
      if (CoreTool.isNullEo(eo) || CoreTool.isNullStr(eo.getId())) {
        throw new CoreException(CoreException.E_PARAM_ERR);
      }
      cronJobMapper.remove(eo);
    } catch (Exception e) {
      if (log.isDebugEnabled()) {
        log.error(e.getMessage(), e);
      }
      throw e;
    }
  }

  /**
   * 定时任务 >>> 批量删除
   */
  @Transactional(propagation = Propagation.REQUIRED, readOnly = false, rollbackFor = Exception.class)
  public void removeBatch(Collection<CronJob> eos) throws Exception {
    try {
      if (CoreTool.isEmpty(eos)) {
        throw new CoreException(CoreException.E_PARAM_ERR);
      }
      LinkedHashSet<String> idAndIn = new LinkedHashSet<>();
      for (CronJob eo : eos) {
        if (CoreTool.isNullEo(eo) || CoreTool.isNullStr(eo.getId())) {
          throw new CoreException(CoreException.E_PARAM_ERR);
        }
        idAndIn.add(eo.getId());
      }
      Collection<CronJob> rCronJobSet = cronJobMapper.query(new CronJobQO().setIdAndIn(idAndIn));
      if (CoreTool.isNotEmpty(rCronJobSet)) {
        for (CronJob eo : rCronJobSet) {
          scheduler.pauseTrigger(new TriggerKey(eo.getJobName() + triggerName, eo.getJobGrouping()));
          scheduler.unscheduleJob(new TriggerKey(eo.getJobName() + triggerName, eo.getJobGrouping()));
          scheduler.deleteJob(new JobKey(eo.getJobName(), eo.getJobGrouping()));
        }
      }
      cronJobMapper.removeBatch(eos);
    } catch (Exception e) {
      if (log.isDebugEnabled()) {
        log.error(e.getMessage(), e);
      }
      throw e;
    }
  }

  /**
   * 定时任务 >>> 数据量
   */
  public Long count(CronJobQO qo) throws Exception {
    try {
      return cronJobMapper.count(qo);
    } catch (Exception e) {
      if (log.isDebugEnabled()) {
        log.error(e.getMessage(), e);
      }
      throw e;
    }
  }

  /**
   * 定时任务 >>> 主键查询
   */
  public CronJob pk(CronJobQO qo) throws Exception {
    try {
      if (CoreTool.isNullEo(qo) || CoreTool.isNullStr(qo.getId())) {
        throw new CoreException(CoreException.E_PARAM_ERR);
      }
      return cronJobMapper.pk(qo);
    } catch (Exception e) {
      if (log.isDebugEnabled()) {
        log.error(e.getMessage(), e);
      }
      throw e;
    }
  }

  /**
   * 定时任务 >>> 查询
   */
  public Collection<CronJob> query(CronJobQO qo) throws Exception {
    try {
      return cronJobMapper.query(qo);
    } catch (Exception e) {
      if (log.isDebugEnabled()) {
        log.error(e.getMessage(), e);
      }
      throw e;
    }
  }

  /**
   * 定时任务 >>> 分页
   */
  public Paging<CronJob> paging(Param<CronJobQO> param) throws Exception {
    try {
      if (CoreTool.isNullEo(param)) {
        throw new CoreException(CoreException.E_PARAM_ERR);
      }
      if (param.getPage() < 1 || param.getRows() < 1) {
        throw new CoreException(CoreException.E_PAGING_PARAM_ERR);
      }
      Paging<CronJob> paging = new Paging<>(param);
      if (CoreTool.isEmpty(param.getData()) || CoreTool.isNullEo(param.get())) {
        Long count = cronJobMapper.count(null);
        paging.setCount(count);
        if (count > 0) {
          if (count <= paging.getOffset()) {
            throw new CoreException(CoreException.E_PAGING_PARAM_ERR);
          }
          paging.setData(cronJobMapper.paging(paging, null));
        }
      } else {
        Long count = cronJobMapper.count(param.get());
        paging.setCount(count);
        if (count > 0) {
          if (count <= paging.getOffset()) {
            throw new CoreException(CoreException.E_PAGING_PARAM_ERR);
          }
          paging.setData(cronJobMapper.paging(paging, param.get()));
        }
      }
      return paging;
    } catch (Exception e) {
      if (log.isDebugEnabled()) {
        log.error(e.getMessage(), e);
      }
      throw e;
    }
  }

  /**
   * 定时任务 >>> 业务数据量
   */
  public Long countVo(CronJobQO qo) throws Exception {
    try {
      return cronJobMapper.countVo(qo);
    } catch (Exception e) {
      if (log.isDebugEnabled()) {
        log.error(e.getMessage(), e);
      }
      throw e;
    }
  }

  /**
   * 定时任务 >>> 业务主键查询
   */
  public CronJobVO pkVo(CronJobQO qo) throws Exception {
    try {
      if (CoreTool.isNullEo(qo) || CoreTool.isNullStr(qo.getId())) {
        throw new CoreException(CoreException.E_PARAM_ERR);
      }
      return cronJobMapper.pkVo(qo);
    } catch (Exception e) {
      if (log.isDebugEnabled()) {
        log.error(e.getMessage(), e);
      }
      throw e;
    }
  }

  /**
   * 定时任务 >>> 业务查询
   */
  public Collection<CronJobVO> queryVo(CronJobQO qo) throws Exception {
    try {
      return cronJobMapper.queryVo(qo);
    } catch (Exception e) {
      if (log.isDebugEnabled()) {
        log.error(e.getMessage(), e);
      }
      throw e;
    }
  }

  /**
   * 定时任务 >>> 业务分页
   */
  public Paging<CronJobVO> pagingVo(Param<CronJobQO> param) throws Exception {
    try {
      if (CoreTool.isNullEo(param)) {
        throw new CoreException(CoreException.E_PARAM_ERR);
      }
      if (param.getPage() < 1 || param.getRows() < 1) {
        throw new CoreException(CoreException.E_PAGING_PARAM_ERR);
      }
      Paging<CronJobVO> paging = new Paging<>(param);
      if (CoreTool.isEmpty(param.getData()) || CoreTool.isNullEo(param.get())) {
        Long count = cronJobMapper.countVo(null);
        paging.setCount(count);
        if (count > 0) {
          if (count <= paging.getOffset()) {
            throw new CoreException(CoreException.E_PAGING_PARAM_ERR);
          }
          paging.setData(cronJobMapper.pagingVo(paging, null));
        }
      } else {
        Long count = cronJobMapper.countVo(param.get());
        paging.setCount(count);
        if (count > 0) {
          if (count <= paging.getOffset()) {
            throw new CoreException(CoreException.E_PAGING_PARAM_ERR);
          }
          paging.setData(cronJobMapper.pagingVo(paging, param.get()));
        }
      }
      return paging;
    } catch (Exception e) {
      if (log.isDebugEnabled()) {
        log.error(e.getMessage(), e);
      }
      throw e;
    }
  }

  /**
   * 定时任务 >>> 开始
   */
  @Transactional(propagation = Propagation.REQUIRED, readOnly = false, rollbackFor = Exception.class)
  public CronJob start(CronJobQO qo) throws Exception {
    CronJob eo = null;
    try {
      if (CoreTool.isNullEo(qo) || CoreTool.isNullStr(qo.getId())) {
        throw new CoreException(CoreException.E_PARAM_ERR);
      }
      eo = cronJobMapper.pk(qo);
      JobDetail jobDetail = JobBuilder.newJob((Class<? extends Job>) Class.forName(eo.getJobClassName()))
          .withIdentity(eo.getJobName(), eo.getJobGrouping())
          .usingJobData(CoreTool.isNullStr(eo.getJobParam()) ? new JobDataMap() : new JobDataMap(gson.fromJson(eo.getJobParam(), new TypeToken<LinkedHashMap<String, Object>>(){}.getType())))
          .build();
      Trigger trigger = TriggerBuilder.newTrigger()
          .withIdentity(eo.getJobName() + triggerName, eo.getJobGrouping())
          .withSchedule(CronScheduleBuilder.cronSchedule(eo.getCronExpression()).withMisfireHandlingInstructionDoNothing())
          .build();
      scheduler.scheduleJob(jobDetail, trigger);
      cronJobMapper.updateSome(new CronJob().setId(eo.getId()).setJobStatus("start"));
      return eo;
    } catch (ObjectAlreadyExistsException e) {
      if (CoreTool.isNullEo(eo)) {
        throw e;
      }
      if (("Unable to store Job : '" + eo.getJobGrouping() + "." + eo.getJobName() + "', because one already exists with this identification.").equals(e.getMessage())) {
        scheduler.pauseTrigger(new TriggerKey(eo.getJobName() + triggerName, eo.getJobGrouping()));
        scheduler.unscheduleJob(new TriggerKey(eo.getJobName() + triggerName, eo.getJobGrouping()));
        scheduler.deleteJob(new JobKey(eo.getJobName(), eo.getJobGrouping()));
        JobDetail jobDetail = JobBuilder.newJob((Class<? extends Job>) Class.forName(eo.getJobClassName()))
            .withIdentity(eo.getJobName(), eo.getJobGrouping())
            .usingJobData(CoreTool.isNullStr(eo.getJobParam()) ? new JobDataMap() : new JobDataMap(gson.fromJson(eo.getJobParam(), new TypeToken<LinkedHashMap<String, Object>>(){}.getType())))
            .build();
        Trigger trigger = TriggerBuilder.newTrigger()
            .withIdentity(eo.getJobName() + triggerName, eo.getJobGrouping())
            .withSchedule(CronScheduleBuilder.cronSchedule(eo.getCronExpression()).withMisfireHandlingInstructionDoNothing())
            .build();
        scheduler.scheduleJob(jobDetail, trigger);
        cronJobMapper.updateSome(new CronJob().setId(eo.getId()).setJobStatus("start"));
        return eo;
      }
      throw e;
    } catch (Exception e) {
      if (log.isDebugEnabled()) {
        log.error(e.getMessage(), e);
      }
      throw e;
    }
  }

  /**
   * 定时任务 >>> 停止
   */
  @Transactional(propagation = Propagation.REQUIRED, readOnly = false, rollbackFor = Exception.class)
  public CronJob stop(CronJobQO qo) throws Exception {
    try {
      if (CoreTool.isNullEo(qo) || CoreTool.isNullStr(qo.getId())) {
        throw new CoreException(CoreException.E_PARAM_ERR);
      }
      CronJob eo = cronJobMapper.pk(qo);
      scheduler.pauseTrigger(new TriggerKey(eo.getJobName() + triggerName, eo.getJobGrouping()));
      scheduler.unscheduleJob(new TriggerKey(eo.getJobName() + triggerName, eo.getJobGrouping()));
      scheduler.deleteJob(new JobKey(eo.getJobName(), eo.getJobGrouping()));
      cronJobMapper.updateSome(new CronJob().setId(eo.getId()).setJobStatus("stop"));
      return eo;
    } catch (Exception e) {
      if (log.isDebugEnabled()) {
        log.error(e.getMessage(), e);
      }
      throw e;
    }
  }

  /**
   * 定时任务 >>> 立即执行一次
   */
  public CronJob executeOnceImmediately(CronJobQO qo) throws Exception {
    try {
      if (CoreTool.isNullEo(qo) || CoreTool.isNullStr(qo.getId())) {
        throw new CoreException(CoreException.E_PARAM_ERR);
      }
      CronJob eo = cronJobMapper.pk(qo);
      JobKey jobKey = new JobKey(eo.getJobName() + executeOnceImmediatelyName, eo.getJobGrouping());
      if (scheduler.checkExists(jobKey)) {
        scheduler.deleteJob(jobKey);
      }
      JobDetail jobDetail = JobBuilder.newJob((Class<? extends Job>) Class.forName(eo.getJobClassName()))
          .withIdentity(eo.getJobName() + executeOnceImmediatelyName, eo.getJobGrouping())
          .usingJobData(CoreTool.isNullStr(eo.getJobParam()) ? new JobDataMap() : new JobDataMap(gson.fromJson(eo.getJobParam(), new TypeToken<LinkedHashMap<String, Object>>(){}.getType())))
          .build();
      Trigger trigger = TriggerBuilder.newTrigger()
          .withIdentity(eo.getJobName() + executeOnceImmediatelyName + triggerName, eo.getJobGrouping())
          .startNow()
          .build();
      scheduler.scheduleJob(jobDetail, trigger);
      return eo;
    } catch (Exception e) {
      if (log.isDebugEnabled()) {
        log.error(e.getMessage(), e);
      }
      throw e;
    }
  }

}
